﻿#pragma once

#include "common.h"
#include <set>
#include <map>

namespace anip
{
	class _bind_notifiable
	{
	protected:
		bool _isVolatile = false;

		// those whom this depends on
		std::set<const _bind_notifiable*> _back;

		// those who depend on this
		mutable std::set<_bind_notifiable*> _forward;

		virtual void processDependencyDestruction(const _bind_notifiable* dep) {}

		void registerBack(const _bind_notifiable& item)
		{
			_back.insert(&item);
			item._forward.insert(this);
			if (item._isVolatile) markVolatile();
		}

		void unregisterBack(const _bind_notifiable& item)
		{
			assert(_back.count(&item) > 0);
			assert(item._forward.count(this) > 0);
			_back.erase(&item);
			item._forward.erase(this);
		}

		void registerForward(_bind_notifiable& item) const
		{
			_forward.insert(&item);
			item._back.insert(this);
			if (_isVolatile) item.markVolatile();
		}

		void updateVolatile()
		{
			markVolatile(false);
			for (auto i : _back)
				if (i->_isVolatile)
				{
					markVolatile();
					break;
				}
		}

	public:
		~_bind_notifiable()
		{
			// don't care if we are just really exiting
			if (anip::initStatus() == InitStatus::Finalized)
				return;

			while (!_forward.empty())
			{
				auto i = *_forward.begin();
				_forward.erase(i);
				assert(i->_back.count(this) > 0);
				i->processDependencyDestruction(this);
				if (i->_back.count(this) > 0)
				{
					//i->_back.erase(this);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wexceptions"
					// PANIC
					throw std::logic_error("destructing _bind_notifiable with dependency");
#pragma clang diagnostic pop
				}
			}

			for (auto i : _back)
			{
				assert(i->_forward.count(this) > 0);
				i->_forward.erase(this);
			}
		}

		virtual void onChanged(bool local = true)
		{
			for (auto i : _forward)
				i->onChanged(false);
		}

		virtual void markVolatile(bool v = true)
		{
			_isVolatile = v;
		}

		friend void swap(_bind_notifiable& first, _bind_notifiable& second)
		{
			for (auto i : first._back)
			{
				i->_forward.erase(&first);
				i->_forward.insert(&second);
			}
			for (auto i : first._forward)
			{
				i->_back.erase(&first);
				i->_back.insert(&second);
			}
			for (auto i : second._back)
			{
				i->_forward.erase(&second);
				i->_forward.insert(&first);
			}
			for (auto i : second._forward)
			{
				i->_back.erase(&second);
				i->_back.insert(&first);
			}

			swap(first._back, second._back);
			swap(first._forward, second._forward);
		}
	};

	struct _tempBindableRoot {};

	template<typename T>
	struct _tempBindableExpr : public _tempBindableRoot
	{
		typedef T ResultType;
	};

	template<typename T>
	class bindable : public _bind_notifiable
	{
	public:
		typedef std::function<void(bindable&)> ChangeListener;
		typedef std::function<T()> ValueProvider;

	private:
		bool _isConstant = false;
		mutable bool _isCached = false;

		mutable T _simpleValue;
		ValueProvider _value;

		std::map<const void*, ChangeListener> 
			_changedListeners;

	protected:
		void processDependencyDestruction(const _bind_notifiable* dep) override
		{
			if (onDependencyDestruction)
				onDependencyDestruction(dep);
		}

		void _clear()
		{
			_isVolatile = false;
			_isCached = false;
			_isConstant = false;
			_value = {};

			while (!_back.empty())
				unregisterBack(**_back.begin());
		}

		void _assign(const bindable& other)
		{
			if (this == &other) return;
			_clear();
			registerBack(other);
			_isConstant = false;
			_value = [&]() { return (T)other; };
		}

		template<typename E,
			std::enable_if_t<std::is_base_of_v<_tempBindableExpr<T>, E>, bool> = true>
		void _assignExpr(E&& expr)
		{
			_clear();
			std::set<const _bind_notifiable*> backs = _tempBindableRefs(expr);
			// ANIP_FIXME: should detect circular dependency
			// i.e. a = expr(b) + 1; b = expr(a) + 1
			for (auto i : backs)
				registerBack(*i);
			_isConstant = false;
			_value = [=]() { return _tempBindableEval(expr); };
		}

	public:
		std::function<void(const _bind_notifiable*)> onDependencyDestruction{};

		bindable()
		{ }

		bindable(const T& constant)
		{
			_isConstant = true;
			_simpleValue = constant;
		}

		bindable(const bindable& other)
		{
			_assign(other);
		}

		bindable(bindable&& other) :
			bindable()
		{
			swap(*this, other);
		}

		bindable(const ValueProvider& provider)
		{
			_isConstant = false;
			_value = provider;
		}

		bindable(ValueProvider&& provider)
		{
			_isConstant = false;
			_value = forward(provider);
		}

		bindable(const ValueProvider& provider, 
			std::initializer_list<const _bind_notifiable*> back) :
			bindable()
		{
			_isConstant = false;
			_value = provider;
			for (auto i : back)
				registerBack(*i);
		}

		bindable(ValueProvider&& provider,
			std::initializer_list<const _bind_notifiable*> back) :
			bindable()
		{
			_isConstant = false;
			_value = std::forward<ValueProvider>(provider);
			for (auto i : back)
				registerBack(*i);
		}

		void onChanged(bool local = true) override
		{
			if (!local)
			{
				_isCached = false;
				updateVolatile();
			}

			for (auto& i : _changedListeners)
				i.second(*this);

			_bind_notifiable::onChanged(local);
		}

		friend void swap(bindable& first, bindable& second)
		{
			using std::swap;

			swap<_bind_notifiable>(first, second);

			swap(first._isCached, second._isCached);
			swap(first._isConstant, second._isConstant);
			swap(first._isVolatile, second._isVolatile);

			swap(first._simpleValue, second._simpleValue);
			swap(first._value, second._value);
			// ANIP_FIXME: should not change listeners!
			swap(first._changedListeners, second._changedListeners);
		}

		/*template<typename E,
			enable_if_t<is_base_of_v<_tempBindableExpr<T>, E>, bool> = true>
		bindable(E&& expr)
		{
			_assignExpr(forward<E>(expr));
		}*/
		
		operator T() const
		{
			return (*this)();
		}

		T operator()() const
		{
			if (_isConstant || _isCached) return _simpleValue;
			if (!_value) throw std::logic_error("evalutaing an uninitialized bindable<>");
			if (!_isVolatile)
			{
				_simpleValue = _value();
				_isCached = true;
				return _simpleValue;
			}
			return _value();
		}

		void markVolatile(bool v = true) override
		{
			_isVolatile = v;
			if (v) _isCached = false;
		}

		void markDependency(const bindable& other)
		{
			registerBack(other);
		}

		bindable& operator=(const T& constant)
		{
			_clear();
			_isConstant = true;
			_simpleValue = constant;
			onChanged();
			return *this;
		}

		bindable& operator=(const bindable& other)
		{
			_assign(other);
			onChanged();
			return *this;
		}

		bindable& operator=(bindable&& other)
		{
			_clear();
			swap(*this, other);
			onChanged();
			return *this;
		}

		bindable& operator=(ValueProvider&& provider)
		{
			_clear();
			_isConstant = false;
			_value = std::forward<ValueProvider>(provider);
			onChanged();
			return *this;
		}

		bindable& operator=(const ValueProvider& provider)
		{
			_clear();
			_isConstant = false;
			_value = provider;
			onChanged();
			return *this;
		}

		template<typename E,
			std::enable_if_t<std::is_base_of_v<_tempBindableExpr<T>, E>, bool> = true>
			bindable& operator=(E&& expr)
		{
			_assignExpr(std::forward<E>(expr));
			onChanged();
			return *this;
		}

		void registerListener(const void* key, const ChangeListener& listener)
		{
			assert(_changedListeners.count(key) == 0);
			_changedListeners.insert({ key, listener });
		}

		void registerListener(const void* key, ChangeListener&& listener)
		{
			assert(_changedListeners.count(key) == 0);
			_changedListeners.insert({ key, std::forward<ChangeListener>(listener) });
		}

		void unregisterListener(const void* key)
		{
			assert(_changedListeners.count(key) > 0);
			_changedListeners.erase(key);
		}
	};

	typedef bindable<int> bint;
	typedef bindable<float> bfloat;
	typedef bindable<vec2> bvec2;
	typedef bindable<color> bcolor;
	typedef bindable<wstring> bwstring;
}

#include "bind_expr.h"